export class Rect {
    constructor(x1, y1, x2, y2) {
        [this.x1, this.y1, this.x2, this.y2] = [x1, y1, x2, y2];
    }
    
    get width() {
        return this.x2 - this.x1;
    }
    get height() {
        return this.y2 - this.y1;
    }
    
    equal(rect) {
        if (rect == null) {
            return false;
        }
        return this.x1 === rect.x1 
            && this.y1 === rect.y1
            && this.x2 === rect.x2
            && this.y2 === rect.y2;
    }
    static equal(rect1, rect2) {
        if (rect1 == null && rect2 == null) {
            return true;
        } else if (rect1 == null || rect2 == null) {
            return false;
        }
        return rect1.equal(rect2);
    }
        
    isNull() {
        return this.x2 <= this.x1 || this.y2 <= this.y1; 
    }
    static isNull(rect) {
        return rect == null || rect.isNull();
    } 
    
    checkIntersection(rect) {
        return Math.abs(rect.x1 + rect.x2 - this.x1 - this.x2)
                < this.x2 - this.x1 + rect.x2 - rect.x1
            && 
                Math.abs(rect.y1 + rect.y2 - this.y1 - this.y2)
                < this.y2 - this.y1 + rect.y2 - rect.y1;
    }    
    static checkIntersection(rect1, rect2) {
        if (rect1 == null || rect2 == null) {
            return false;
        }
        return rect1.checkIntersection(rect2);     
    }
      
    intersect(rect) {
        if (rect == null || rect.isNull()) {
            return null;
        }
        if (this.isNull()) {
            return null;
        }
        
        let r = new Rect(
            Math.max(this.x1, rect.x1),
            Math.max(this.y1, rect.y1),
            Math.min(this.x2, rect.x2),
            Math.min(this.y2, rect.y2)
        );
        
        return r.x1 >= r.x2 || r.y1 >= r.y2 ? null : r;
    }
    static intersect(rect1, rect2) {
        return rect1.intersect(rect2);
    }          
       
    subtract(rect) {
        if (rect == null || this.isNull() || rect.isNull()) {
            return this;
        }
        
        let r = this.intersect(rect);
        if (r == null) {
            return this;
        }
        if (r.equal(this)) {
            return null;
        }
        
        let _1_x1 = this.x1,
            _1_y1 = this.y1,
            _1_x2 = this.x2,
            _1_y2 = this.y2,
            _r_x1 = r.x1,
            _r_y1 = r.y1,
            _r_x2 = r.x2,
            _r_y2 = r.y2;
        
        //  ___________
        // |_____1_____|
        // |  |     |  |
        // |2 |  r  | 3| rect1
        // |  |_____|  |
        // |__|__4__|__|
        let rest = [
                new Rect(_1_x1, _1_y1, _1_x2, _r_y1), // 1
                new Rect(_1_x1, _r_y1, _r_x1, _1_y2), // 2
                new Rect(_r_x2, _r_y1, _1_x2, _1_y2), // 3
                new Rect(_r_x1, _r_y2, _r_x2, _1_y2)  // 4
        ];
        return rest.filter(r => !r.isNull());  
    }
    static subtract(rect1, rect2) {
        if (rect1 == null || rect2 == null) {
            return rect1;
        }
        return rect1.subtract(rect2);
    }
}

const ZOOM_MAX = 8,
      ZOOM_MIN = 0.1;

export class Canvas {
    constructor(canvas, width = 512, height = 512,
                 bgcolor = "#000000", clearBeforePaint = true) {
        try {
            this.ctx = canvas.getContext("2d");
            this.canvas = canvas;
        } catch (ex) {
            throw new Error("Not a valid canvas dom node!");
        }
        this.clear = clearBeforePaint;        
        this.ctx.fillStyle = bgcolor;
        
        this.canvasRect = new Rect(0, 0, width, height);
        this.width = width;
        this.height = height;
        
        this.drawingArea = new Rect(0, 0, width, height); 
        
        this.img = this.toImage();
        this.imgCenter = {x: 0, y: 0};
        
        this.zoomRatio = 1;  
    }
    
    set clearBeforePaint(value) {
        if (typeof value === "boolean") {
            this.clear = value;
        }
    }
    get clearBeforePaint() {
        return this.clear;
    }
    
    set bgcolor(value) {
        this.ctx.fillStyle = value;
    }
    
    toImage(format = "jpeg") {
        this.img = new Image();
        this.img.src = this.canvas.toDataURL(`image/${format}`);
        return this.img;
    }
    
    setImage(img) {
        this.img = img;
    }    
    
    translateTo(x = 0, y = 0) {
        let imgRect = new Rect(x, y, x + this.width, y + this.height),
            rectToDraw = imgRect.intersect(this.canvasRect);
        
        if (this.clearBeforePaint && this.drawingArea) {
            // not clear actually, just paint the bgcolor
            // because painting the solid color is faster than clearing (based my test :) ) 
            
            let rectsToClear = this.drawingArea.subtract(rectToDraw);
            if (rectsToClear && rectsToClear.length > 0) {
                for (let i = 0, len = rectsToClear.length; i < len; i++) {
                    let r = rectsToClear[i];
                    this.ctx.rect(r.x1, r.y1, r.width, r.height);
                }
                this.ctx.fill();
            }
        }
        
        this.drawingArea = rectToDraw;  
        
        if (rectToDraw && !rectToDraw.isNull()) {
            let sx = x >= 0 ? 0 : -x,
                sy = y >= 0 ? 0 : -y,
                dw = rectToDraw.width,
                dh = rectToDraw.height,
                dx = rectToDraw.x1,
                dy = rectToDraw.y1;
            
            this.ctx.drawImage(this.img, sx, sy, dw, dh, dx, dy, dw, dh); 
        }         
    }
    
    zoom(ratio = 1) {
        if (typeof ratio !== "number") {
            throw new Error(`Not a valid ratio: must be a number which is between ${ZOOM_MIN} and ${ZOOM_MAX}`);
        }
        if (ratio < ZOOM_MIN || ratio > ZOOM_MAX || ratio === this.zoomRatio) {
            return;
        }
        
        
        
        
        this.zoomRatio = ratio;
    }
}